package gestIT;

/**
 * Subclass of TmpExp that represent a binary building block.
 * A complex building block can represent the operator of ImmediateChoice, Choice, Parallel, Disabling, OrderIndependence, SynchronizedParallel or Enabling 
 *
 * @author nicola
 *
 */
public class BinaryTmpExp extends TmpExp {

	/** The left operand of the binary building block */
	private TmpExp LeftOperand;
	/** The right operand of the binary building block */
	private TmpExp RightOperand;
	
	/**
	 * Return the left operand of the building block
	 * @return The left operand
	 */
	public TmpExp getLeftOperand(){
		return LeftOperand;
	}
	
	/**
	 * Return the right operand of the building block
	 * @return The right operand
	 */
	public TmpExp getRightOperand(){
		return RightOperand;
	}
	
	/**
	 * Set the left operand of the building block
	 * @param exp The left operand to be setted
	 */
	public void setLeftOperand(TmpExp exp){
		LeftOperand = exp;
		if (exp != null){
			LeftOperand.addOnStateChangedListener(new StateChangedHandler() {
				@Override
				public void OnStateChanged(StateChangedEvent evt) {
					BinaryTmpExp_OnStateChanged(evt);
					
				}
			});
			LeftOperand.Parent = this;
		}
	}
	
	/**
	 * Set the right operand of the building block
	 * @param exp The right operand to be setted
	 */
	public void setRightOperand(TmpExp exp){
		RightOperand = exp;
		if (exp != null){
			RightOperand.addOnStateChangedListener(new StateChangedHandler() {
				@Override
				public void OnStateChanged(StateChangedEvent evt) {
					BinaryTmpExp_OnStateChanged(evt);
					
				}
			});
			RightOperand.Parent = this;
		}
	}
	
	/** The Operator of the binary building block */
	public TmpOperator Operator;
	
	/** Boolean flag that indicate if left operand is completed */
	private boolean leftCompleted;
	/** Boolean flag that indicate if right operand is completed */
	private boolean rightCompleted;
	
	/**
	 * Constructor that return a new empty BinaryTmpExp
	 */
	public BinaryTmpExp(){
		this.Operator = TmpOperator.None;
		setLeftOperand(null);
		setRightOperand(null);
		this.type = ExpType.Binary;
	}
	
	/**
	 * Constructor that return a new BinaryTmpExp, setting is left and right operand and the operator
	 * @param leftOperand The left operand of the new binary building block
	 * @param rightOperand The right operand of the new binary building block
	 * @param op The new operator to be setted
	 */
	public BinaryTmpExp(TmpExp leftOperand, TmpExp rightOperand, TmpOperator op){
		this.type = ExpType.Binary;
		setLeftOperand(leftOperand);
		setRightOperand(rightOperand);
		this.Operator = op;
	}
	
	@Override
	public void SetExecState(ExecState st, Token tk){
		super.SetExecState(st, tk);
		switch (st) {
		case Disabled:
			if (this.LeftOperand != null)
				this.LeftOperand.SetExecState(ExecState.Disabled, tk);
			if (this.RightOperand != null)
				this.RightOperand.SetExecState(ExecState.Disabled, tk);
			break;

		case Enabled:
			
			switch (this.Operator) {
			
			case ImmediateChoice: case Choice: case Parallel: case Disabling: case OrderIndependence: case SynchronizedParallel: 
				if (this.Operator == TmpOperator.SynchronizedParallel)
					this.leftCompleted = false;
					this.rightCompleted = false;
				
				if (this.LeftOperand != null)
					this.LeftOperand.SetExecState(ExecState.Enabled, tk);

                if (this.RightOperand != null)
                    this.RightOperand.SetExecState(ExecState.Enabled, tk);
				
				break;
			
			case Enabling:
                if (this.LeftOperand != null)
                    this.LeftOperand.SetExecState(ExecState.Enabled, tk);
                
                break;
			}
			break;
			
		default:
			break;
		}
	}
	
	private void BinaryTmpExp_OnStateChanged(StateChangedEvent evt){

		TmpExp sender = evt.getSender();
		ExpEventArgs arg = evt.getEventArgs();
		Token tk = evt.getToken();
		
		if (this.Operator != TmpOperator.None){
			switch (sender.state) {
			case Error:
				this.SetExecState(ExecState.Error, tk);
				RaiseError(this, arg, tk);
				RaiseOnStateChanged(this, arg, tk);
				if (this.Iterative && tk != null)
                {
                    this.SetExecState(ExecState.Enabled, tk);
                }
				break;
			
			case Performing:
				ApplyPerforming(sender, arg, tk);
				break;
				
			case Completed:
				ApplyComplete(sender, arg, tk);
				break;
			}
		}	
	}
	
	private void ApplyPerforming(TmpExp exp, ExpEventArgs args, Token token){
		switch (this.Operator) {
		
		case ImmediateChoice:
			if (IsLeftChild(exp))
				// executes the left operand of a choice,
                // disables the right operand
                this.RightOperand.SetExecState(ExecState.Disabled, token);
			else if (IsRightChild(exp))
                // execute the right operand of a choice, 
                // disable the left operand
                this.LeftOperand.SetExecState(ExecState.Disabled, token);
			this.SetExecState(ExecState.Performing, token);
			RaisePerforming(this, args, token);			
			break;
			
		case Choice:
		    // no action in this case, we wait until one of the two 
            // operands completes
            this.SetExecState(ExecState.Performing, token);
            RaisePerforming(this, args, token);
            RaiseOnStateChanged(this, args, token);
            break;
            
		case Parallel: case SynchronizedParallel: break;
		
		case Disabling:
			if (IsRightChild(exp))
				this.LeftOperand.SetExecState(ExecState.Performing, token);
	
			this.SetExecState(ExecState.Performing, token);
			RaisePerforming(this, args, token);
			RaiseOnStateChanged(this, args, token);
			break;
			
		case Enabling:
            if (IsLeftChild(exp))
                // disable the right operand during the execution of the left one
                this.RightOperand.SetExecState(ExecState.Disabled, token);

            else if (IsRightChild(exp))
                // disable the left operand, that should be already completed
                this.LeftOperand.SetExecState(ExecState.Disabled, token);
            
            this.SetExecState(ExecState.Performing, token);
            RaisePerforming(this, args, token);
            RaiseOnStateChanged(this, args, token);
            break;

		default:
			break;
		}
	}
	
	
	private void ApplyComplete(TmpExp exp, ExpEventArgs args, Token token){
		switch (this.Operator) {

		case Choice: case ImmediateChoice:
			
			if (IsLeftChild(exp))
				this.RightOperand.SetExecState(ExecState.Disabled, token);
			if (IsRightChild(exp))
				this.LeftOperand.SetExecState(ExecState.Disabled, token);
			
			this.SetExecState(ExecState.Completed, token);
			RaiseCompleted(this, args, token);
			RaiseOnStateChanged(this, args, token);
			break;
			
			
		case SynchronizedParallel:
			if (IsLeftChild(exp) || this.LeftOperand.Optional || this.LeftOperand.state == ExecState.Completed)
				this.leftCompleted = true;
			if (IsRightChild(exp) || this.RightOperand.Optional || this.RightOperand.state == ExecState.Completed)
				this.rightCompleted = true;
			
			if (this.leftCompleted && this.rightCompleted){
			
				this.SetExecState(ExecState.Completed, token);
				RaiseCompleted(this, args, token);
				RaiseOnStateChanged(this, args, token);
			} else {
				this.SetExecState(ExecState.Performing, token);
				RaisePerforming(this, args, token);
				RaiseOnStateChanged(this, args, token);
			}
			break;
	
		case Parallel:
            // differently from synchronized parallel we do not 
            // track the execution of the two operands
            this.SetExecState(ExecState.Completed, token);
            RaiseCompleted(this, args, token);
            RaiseOnStateChanged(this, args, token);
            break;
			
		case Disabling:
			if (IsRightChild(exp)){
				this.LeftOperand.SetExecState(ExecState.Disabled, token);
				this.SetExecState(ExecState.Completed, token);
				RaiseCompleted(this, args, token);
				RaiseOnStateChanged(this, args, token);
			} else {
				this.SetExecState(ExecState.Performing, token);
				RaisePerforming(this, args, token);
				RaiseOnStateChanged(this, args, token);
			}
			break;
			
		case Enabling:
			if (IsLeftChild(exp)){
				if (this.RightOperand.Optional){
					this.SetExecState(ExecState.Completed, token);
					this.RightOperand.SetExecState(ExecState.Completed, token);
					RaiseCompleted(this, args, token);
					RaiseOnStateChanged(this, args, token);
				} else {
					this.RightOperand.SetExecState(ExecState.Enabled, token);
					this.SetExecState(ExecState.Performing, token);
					RaisePerforming(this, args, token);
					RaiseOnStateChanged(this, args, token);
				}
			} else if (IsRightChild(exp)){
				this.SetExecState(ExecState.Completed, token);
				RaiseCompleted(this, args, token);
				RaiseOnStateChanged(this, args, token);
			}
		default:
			break;
		}
		
		if (this.Iterative && (this.state == ExecState.Completed || this.state == ExecState.Error))
			this.SetExecState(ExecState.Enabled, token);
		
	}
	
	/**
	 * Check if the the specified gesture is the left operand
	 * @param exp The gesture to be checked
	 * @return True if the gesture is the same as the left operand
	 */
    private boolean IsLeftChild(TmpExp exp)
    {
        if (this.LeftOperand != null && this.LeftOperand.equals(exp))
            return true;
        else
            return false;
    }

    /**
	 * Check if the the specified gesture is the right operand
	 * @param exp The gesture to be checked
	 * @return True if the gesture is the same as the right operand
	 */
    private boolean IsRightChild(TmpExp exp)
    {
        if (this.RightOperand != null && this.RightOperand.equals(exp))
            return true;
        else
            return false;
    }
	
}
